package org.zoomdev.zoom.dao.adapters.parameter;

import org.zoomdev.zoom.common.caster.Caster;
import org.zoomdev.zoom.common.caster.TypeKey;
import org.zoomdev.zoom.dao.DaoException;
import org.zoomdev.zoom.dao.adapters.ParameterAdapter;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.time.*;
import java.util.HashMap;
import java.util.concurrent.ConcurrentHashMap;

public class ParameterAdapterRegistry {
    private static final ConcurrentHashMap<TypeKey, ParameterAdapter<?>> adapterMap = new ConcurrentHashMap<>();

    private static final HashMap<Class<?>, ParameterAdapter<?>> rawMap = new HashMap<>();

    static {
        // 注册默认适配器
        registerRaw(int.class, new IntParameterAdapter());
        registerRaw(Integer.class, new IntParameterAdapter());
        registerRaw(long.class, new LongParameterAdapter());
        registerRaw(Long.class, new LongParameterAdapter());
        registerRaw(float.class, new FloatParameterAdapter());
        registerRaw(Float.class, new FloatParameterAdapter());
        registerRaw(double.class, new DoubleParameterAdapter());
        registerRaw(Double.class, new DoubleParameterAdapter());
        registerRaw(short.class, new ShortParameterAdapter());
        registerRaw(Short.class, new ShortParameterAdapter());
        registerRaw(byte.class, new ByteParameterAdapter());
        registerRaw(Byte.class, new ByteParameterAdapter());
        registerRaw(boolean.class, new BooleanParameterAdapter());
        registerRaw(Boolean.class, new BooleanParameterAdapter());
        registerRaw(String.class, new StringParameterAdapter());
        registerRaw(String.class, new CharParameterAdapter());
        registerRaw(java.sql.Date.class, new SqlDateParameterAdapter());
        registerRaw(java.sql.Time.class, new SqlTimeParameterAdapter());
        registerRaw(java.sql.Timestamp.class, new SqlTimestampParameterAdapter());
        registerRaw(LocalDate.class, new LocalDateParameterAdapter());
        registerRaw(LocalTime.class, new LocalTimeParameterAdapter());
        registerRaw(LocalDateTime.class, new LocalDateTimeParameterAdapter());
        registerRaw(BigDecimal.class, new BigDecimalParameterAdapter());
        registerRaw(BigInteger.class, new BigIntegerParameterAdapter());
        registerRaw(byte[].class, new BytesParameterAdapter());


        registerPair(Year.class, java.sql.Date.class, new YearParameterAdapter());
        registerPair(Instant.class, java.sql.Timestamp.class, new Instant2TimestampParameterAdapter());
    }

    public static <T> void registerRaw(Class<T> type, ParameterAdapter<T> adapter) {
        rawMap.put(type, adapter);
    }

    public static void registerPair(Type fieldType, Class<?> type, ParameterAdapter<?> adapter) {
        adapterMap.put(TypeKey.of(fieldType, type), adapter);
    }

    public static ParameterAdapter<?> getAdapter(Field field, Class<?> columnType) {
        if (columnType == null) {
            throw new NullPointerException();
        }
        Class<?> fieldType = field.getType();
        if (fieldType == columnType) {
            return rawMap.get(columnType);
        }

        Type fieldGenericType = field.getGenericType();
        TypeKey key = TypeKey.of(fieldGenericType, columnType);
        ParameterAdapter<?> adapter = adapterMap.get(key);
        if (adapter == null) {
            if (fieldGenericType instanceof ParameterizedType) {
                if (columnType == String.class) {
                    adapter = JsonStringParameterAdapter.ADAPTER;
                } else if (columnType == byte[].class) {
                    adapter = JsonByteArrayParameterAdapter.ADAPTER;
                } else {
                    throw new DaoException("不支持的类型转换: field:" + fieldGenericType + " column:" + columnType);
                }
            } else if (fieldGenericType instanceof Class) {
                adapter = createAdapter((Class<?>) fieldGenericType, columnType);
            } else {
                adapter = createAdapter(field.getType(), columnType);
            }
            adapterMap.putIfAbsent(key, adapter);
        }

        return adapter;
    }

    public static boolean isPrimitive(Class<?> fieldType) {
        return fieldType.isPrimitive() || fieldType == Integer.class
                || fieldType == Long.class || fieldType == Double.class
                || fieldType == Float.class || fieldType == Short.class
                || fieldType == Character.class || fieldType == Byte.class
                || fieldType == Boolean.class;
    }

    public static ParameterAdapter<?> getAdapter(Class<?> columnType) {
        return ObjectParameterAdapter.OBJECT_PARAMETER_ADAPTER;
    }

    private static ParameterAdapter<?> createAdapter(Class<?> fieldType, Class<?> columnType) {
        if (columnType.isAssignableFrom(fieldType)) {
            return rawMap.get(columnType);
        }
        if (isPrimitive(fieldType)) {
            if (columnType == Integer.class) {
                return rawMap.get(fieldType);
            }
            if (columnType == String.class) {
                return ToStringParameterAdapter.ADAPTER;
            }
        } else if (fieldType.isEnum()) {
            if (columnType == String.class) {
                return ToStringParameterAdapter.ADAPTER;
            }
            if (columnType == Integer.class) {
                return EnumOrdinalParameterAdapter.ADAPTER;
            }
            throw new DaoException("不能将" + fieldType.getName() + "转化到数据库类型" + fieldType);
        }

        return new CasterProxyStatementAdapter(Caster.wrap(fieldType, columnType), rawMap.get(columnType));
    }
}